DESCARGA Y ANÁLISIS DE DATOS GEOGRÁFICOS DESDE RSTUDIO

Hasta acá estuvimos trabajando con datos que fueron descargados de portales open data oficiales, pero esa no es la única fuente de información que existe para analizar Ciudades. Por suerte hay varios repositorios online de donde podemos descargarnos información georreferenciada muy interesante, cómo por ejemplo: OpenStreetMap (OSM, https://www.openstreetmap.org/)

¿Qué es y cómo funciona OpenStreetMap?

OSM es una plataforma creada por una gran comunidad de colaboradores que aportan datos geoespaciales de muchas Ciudades del mundo (calles, rutas, restaurants, museos, hospitales, escuelas, etc.) y los mantienen actualizados. Esta información es consumida por miles de sitios web y aplicaciones ya que puede ser utilizada libremente para cualquier propósito siempre y cuando se respeten los derechos de autor y licencia. Se puede consultar más información en la wiki de OSM.

Por suerte, en R existe un paquete llamado osmdata que nos permitirá conectarnos directamente a OSM y descargarnos toda la información que necesitemos. Así que, comencemos activando las ya conocidas tidyverse, sf y ggmap e instalemos y activemos osmdata:

#install.packages("tidyverse")
library(tidyverse)
#install.packages("sf")
library(sf)
#install.packages("ggmap")
library(ggmap)
#install.packages("osmdata")
library(osmdata)

Cualquier duda sobre el paquete osmdata pueden consultarlo en su documentación oficial.

¿Qué información podemos buscar?

Antes de comenzar a descargar información, utilicemos unos minutos para analizar y conocer que información tenemos disponible. En este este link podrán encontrar una lista detallada de las categorías y tipo de elementos que existen en OSM, pero si quisiésemos también podríamos hacer algunas consultas desde RStudio con available_features() y available_tags(). La primer función nos permitirá obtener el encabezado (nombre de las columnas) de la información que contiene la base de datos que nos podemos descargar:

available_features()
##   [1] "4wd_only"                "abandoned"              
##   [3] "abutters"                "access"                 
##   [5] "addr"                    "addr:city"              
##   [7] "addr:conscriptionnumber" "addr:country"           
##   [9] "addr:district"           "addr:flats"             
##  [11] "addr:full"               "addr:hamlet"            
##  [13] "addr:housename"          "addr:housenumber"       
##  [15] "addr:inclusion"          "addr:interpolation"     
##  [17] "addr:place"              "addr:postbox"           
##  [19] "addr:postcode"           "addr:province"          
##  [21] "addr:state"              "addr:street"            
##  [23] "addr:subdistrict"        "addr:suburb"            
##  [25] "addr:unit"               "admin_level"            
##  [27] "aeroway"                 "agricultural"           
##  [29] "alt_name"                "amenity"                
##  [31] "area"                    "atv"                    
##  [33] "backward"                "barrier"                
##  [35] "basin"                   "bdouble"                
##  [37] "bicycle"                 "bicycle_road"           
##  [39] "biergarten"              "boat"                   
##  [41] "border_type"             "boundary"               
##  [43] "brand"                   "bridge"                 
##  [45] "building"                "building:colour"        
##  [47] "building:fireproof"      "building:flats"         
##  [49] "building:levels"         "building:material"      
##  [51] "building:min_level"      "building:part"          
##  [53] "building:soft_storey"    "bus_bay"                
##  [55] "busway"                  "capacity"               
##  [57] "castle_type"             "change"                 
##  [59] "charge"                  "construction"           
##  [61] "construction#Railways"   "covered"                
##  [63] "craft"                   "crossing"               
##  [65] "crossing:island"         "cuisine"                
##  [67] "cutting"                 "cycleway"               
##  [69] "denomination"            "destination"            
##  [71] "diet"                    "direction"              
##  [73] "dispensing"              "disused"                
##  [75] "drive_in"                "drive_through"          
##  [77] "ele"                     "electric_bicycle"       
##  [79] "electrified"             "embankment"             
##  [81] "embedded_rails"          "emergency"              
##  [83] "end_date"                "entrance"               
##  [85] "est_width"               "fee"                    
##  [87] "fire_object:type"        "fire_operator"          
##  [89] "fire_rank"               "foot"                   
##  [91] "footway"                 "ford"                   
##  [93] "forestry"                "forward"                
##  [95] "frequency"               "fuel"                   
##  [97] "gauge"                   "golf_cart"              
##  [99] "goods"                   "hazmat"                 
## [101] "healthcare"              "healthcare:counselling" 
## [103] "healthcare:speciality"   "height"                 
## [105] "hgv"                     "highway"                
## [107] "historic"                "horse"                  
## [109] "ice_road"                "incline"                
## [111] "industrial"              "inline_skates"          
## [113] "inscription"             "int_name"               
## [115] "internet_access"         "junction"               
## [117] "kerb"                    "landuse"                
## [119] "lanes"                   "lanes:bus"              
## [121] "lanes:psv"               "layer"                  
## [123] "leaf_cycle"              "leaf_type"              
## [125] "leisure"                 "lhv"                    
## [127] "lit"                     "loc_name"               
## [129] "location"                "man_made"               
## [131] "max_level"               "maxaxleload"            
## [133] "maxheight"               "maxlength"              
## [135] "maxspeed"                "maxstay"                
## [137] "maxweight"               "maxwidth"               
## [139] "military"                "min_level"              
## [141] "minspeed"                "mofa"                   
## [143] "moped"                   "motor_vehicle"          
## [145] "motorboat"               "motorcar"               
## [147] "motorcycle"              "motorroad"              
## [149] "mountain_pass"           "mtb:description"        
## [151] "mtb:scale"               "name"                   
## [153] "name:left"               "name:right"             
## [155] "name_1"                  "name_2"                 
## [157] "narrow"                  "nat_name"               
## [159] "natural"                 "noexit"                 
## [161] "non_existent_levels"     "nudism"                 
## [163] "office"                  "official_name"          
## [165] "old_name"                "oneway"                 
## [167] "opening_hours"           "operator"               
## [169] "organic"                 "oven"                   
## [171] "overtaking"              "parking:condition"      
## [173] "parking:lane"            "passing_places"         
## [175] "place"                   "power"                  
## [177] "priority_road"           "produce"                
## [179] "proposed"                "protected_area"         
## [181] "psv"                     "public_transport"       
## [183] "railway"                 "railway:preserved"      
## [185] "railway:track_ref"       "recycling_type"         
## [187] "ref"                     "reg_name"               
## [189] "religion"                "residential"            
## [191] "roadtrain"               "route"                  
## [193] "sac_scale"               "sauna"                  
## [195] "service"                 "service_times"          
## [197] "shelter_type"            "shop"                   
## [199] "short_name"              "shower"                 
## [201] "sidewalk"                "site"                   
## [203] "ski"                     "smoothness"             
## [205] "social_facility"         "sorting_name"           
## [207] "speed_pedelec"           "start_date"             
## [209] "step_count"              "substation"             
## [211] "surface"                 "tactile_paving"         
## [213] "tank"                    "tidal"                  
## [215] "toilets:wheelchair"      "toll"                   
## [217] "topless"                 "tourism"                
## [219] "tracks"                  "tracktype"              
## [221] "traffic_calming"         "traffic_sign"           
## [223] "trail_visibility"        "trailblazed"            
## [225] "trailblazed:visibility"  "tunnel"                 
## [227] "turn"                    "type"                   
## [229] "usage"                   "vehicle"                
## [231] "vending"                 "voltage"                
## [233] "water"                   "wheelchair"             
## [235] "wholesale"               "width"                  
## [237] "winter_road"             "wood"

Tenemos 238 keys, y como verán, son muy variadas. A su vez, dentro de cada key (amenity, shop, railway, etc.) encontraremos diferentes tags que podemos consultarlos de la siguiente forma:

available_tags("amenity")
##   [1] "animal_boarding"        "animal_breeding"        "animal_shelter"        
##   [4] "arts_centre"            "atm"                    "baby_hatch"            
##   [7] "baking_oven"            "bank"                   "bar"                   
##  [10] "bbq"                    "bench"                  "bicycle_parking"       
##  [13] "bicycle_rental"         "bicycle_repair_station" "biergarten"            
##  [16] "boat_rental"            "boat_sharing"           "brothel"               
##  [19] "bureau_de_change"       "bus_station"            "cafe"                  
##  [22] "car_rental"             "car_sharing"            "car_wash"              
##  [25] "casino"                 "charging_station"       "childcare"             
##  [28] "cinema"                 "clinic"                 "clock"                 
##  [31] "college"                "community_centre"       "conference_centre"     
##  [34] "courthouse"             "crematorium"            "dentist"               
##  [37] "dive_centre"            "doctors"                "dog_toilet"            
##  [40] "drinking_water"         "driving_school"         "events_venue"          
##  [43] "fast_food"              "ferry_terminal"         "fire_station"          
##  [46] "food_court"             "fountain"               "fuel"                  
##  [49] "funeral_hall"           "gambling"               "give_box"              
##  [52] "grave_yard"             "grit_bin"               "hospital"              
##  [55] "hunting_stand"          "ice_cream"              "internet_cafe"         
##  [58] "kindergarten"           "kitchen"                "kneipp_water_cure"     
##  [61] "language_school"        "library"                "lounger"               
##  [64] "love_hotel"             "marketplace"            "monastery"             
##  [67] "motorcycle_parking"     "music_school"           "nightclub"             
##  [70] "nursing_home"           "parcel_locker"          "parking"               
##  [73] "parking_entrance"       "parking_space"          "pharmacy"              
##  [76] "photo_booth"            "place_of_mourning"      "place_of_worship"      
##  [79] "planetarium"            "police"                 "post_box"              
##  [82] "post_depot"             "post_office"            "prison"                
##  [85] "pub"                    "public_bath"            "public_bookcase"       
##  [88] "public_building"        "ranger_station"         "recycling"             
##  [91] "refugee_site"           "restaurant"             "sanitary_dump_station" 
##  [94] "school"                 "shelter"                "shower"                
##  [97] "social_centre"          "social_facility"        "stripclub"             
## [100] "studio"                 "swingerclub"            "taxi"                  
## [103] "telephone"              "theatre"                "toilets"               
## [106] "townhall"               "toy_library"            "university"            
## [109] "vehicle_inspection"     "vending_machine"        "veterinary"            
## [112] "waste_basket"           "waste_disposal"         "waste_transfer_station"
## [115] "water_point"            "watering_place"

Podemos ver que, por ejemplo dentro de la key llamada amenity se encuentran 116 tags o values diferentes como por ejemplo: bar, restaurant, heladería, hospital, biblioteca, etc.

Cabe destacar que, para poder hacer la descarga de datos, es importante que tengamos bien claro que información es la que necesitamos y con que “etiquetas” aparece en OSM.

¿De qué lugar del mundo queremos datos georreferenciados?

Llegó el momento de decidir el sitio/lugar de donde queremos traernos información ya que no podremos descargar todos los datos que tiene OSM porque son millones! Para esto debemos crear un cuadro delimitador o bounding box con la función getbb() del paquete osmdata y escribir dentro el nombre de la Ciudad de interés.

En nuestro caso, trabajaremos con la Comuna 1 de la Ciudad Autónoma de Buenos Aires, que está integrada por los barrios de Retiro, San Nicolás, Puerto Madero, San Telmo, Montserrat y Constitución. Para conocer más sobre las 15 Comunas de la CABA pueden visitar este link.

bbox_comuna1 <- getbb("Comuna 1, Ciudad Autonoma de Buenos Aires, Buenos Aires, Argentina")

Es importante tener en cuenta que el lugar del mundo que estemos buscando tiene que estar escrito con el mayor detalle posible (por ejemplo, “Partido, Ciudad, Provincia, País”), así se evita que OSM descargue información de otro sitio que no sea el elegido. Para conocer el nombre exacto con el que OSM reconoce los lugares, pueden hacer una búsqueda rápida directamente en su sitio web.

Revisemos el resultado que obtuvimos:

bbox_comuna1
##         min       max
## x -58.39294 -58.33975
## y -34.63411 -34.57800

La función anterior devolvió 4 coordenadas (longitud máxima, longitud mínima, latitud máxima, latitud mínima) que representan los límites de la información a descargar. Esto nos servirá como insumo principal tanto al momento de descargar un mapa base como al momento de descargar información geográfica de OSM.

Comencemos descargando un mapa base a partir de la librería ggmap. Cabe destacar que, este paquete utiliza recursos de diferentes APIs (Google, Stamenmap, etc), y por lo tanto es necesario tener conexión a internet al momento de utilizarlo. Cualquier duda pueden consultar la documentación de ggmap.

La función que vamos a usar para descargar el “mapa base” se llama get_stamenmap() y se conecta a los mapas de “Stamen Maps”. Para más información pueden visitar este link

Esta función necesita como mínimo los siguientes parámetros:

  • bbox, donde hay que asignar la bounding box calculada previamente.
  • maptype, donde tenemos que elegir la “estética” de nuestro mapa base. En este caso utilizaremos un mapa de tipo “toner-lite” pero hay otras opciones en este link.
  • zoom, donde indicamos el nivel de “zoom”. Se recomienda que sea entre 10 y 15 dependiendo de la escala del mapa.
mapa_comuna1 <- get_stamenmap(bbox=bbox_comuna1,
                           maptype="toner-lite",
                           zoom=14)

Con ggmap() veamos que nos hemos descargado:

ggmap(mapa_comuna1)

Listo! Parece que todo salió bien! El mapa efectivamente es de la Comuna 1 de la Ciudad Autónoma de Buenos Aires, Buenos Aires, Argentina.

Para poder comprender mejor que extensión de territorio cubre la Comuna 1, sería muy útil que también tengamos “mapeados” los límites de la Comuna. Y por suerte esto podemos conseguirlo agregando el parámetro format_out=“sf_polygon” en la función getbb() de la siguiente forma:

poligono_comuna1 <- getbb("Comuna 1, Ciudad Autonoma de Buenos Aires, Buenos Aires, Argentina",
                            format_out = "sf_polygon")

Veamos el resultado:

poligono_comuna1
## Simple feature collection with 1 feature and 0 fields
## Geometry type: POLYGON
## Dimension:     XY
## Bounding box:  xmin: -58.39294 ymin: -34.63411 xmax: -58.33975 ymax: -34.578
## Geodetic CRS:  WGS 84
##                         geometry
## 1 POLYGON ((-58.39294 -34.599...

Veamos como queda el mapa base realizado con ggmap() si le superponemos una capa de tipo geom_sf() con el contorno de la Comuna 1. Cabe destacar que, a diferencia de los mapas realizados con ggplot(), cuando usamos ggmap(), debemos agregarle el parámetro inherit.aes = FALSE a cada geom_sf() que agreguemos.

ggmap(mapa_comuna1)+
  geom_sf(data=poligono_comuna1, inherit.aes = FALSE)

Lo logramos! Hagamos algunos ajustes para mejorar la visualización:

  • Quitemos el relleno del polígono y demos algún color al borde.
  • Pongamos título, subtítulo y fuente.
  • Ajustemos el theme.
ggmap(mapa_comuna1)+
  geom_sf(data=poligono_comuna1, fill=NA, color="blue", size=1, inherit.aes = FALSE)+
  labs(title="Comuna 1",
       subtitle="Ciudad Autonoma de Buenos Aires",
       caption="Fuente: Open Street Map")+
  theme_void()

¿Cómo descargamos información georreferenciada?

Para traernos información de OSM usaremos 3 funciones del paquete osmdata:

  • opq() para indicar la bounding box correspondiente al lugar del mundo donde queremos descargarnos datos.

  • add_osm_feature() para generar la consulta o requerimiento concreto, detallando key y value.

  • osmdata_sf() para hacer la descarga propiamente dicha, y basada en la información brindada en las 2 funciones anteriores.

Tengamos en cuenta que con osmdata podemos descargar solamente datos vectoriales (geometrías) representados a partir de líneas, puntos y polígonos. Veamos un ejemplo.

Descarga de “puntos”

Como primer paso, asignemos el bounding box correspondiente a nuestro área de estudio dentro de opq() y en add_osm_feature() especifiquemos que key y/o values queremos.

En este caso probemos descargando los restaurantes y bares (puntos) ubicados en la Comuna 1, y que en OSM los encontramos con la key = “amenity” y el value = c(“restaurant”, “bar”):

gastronomia_comuna1 <- opq(bbox_comuna1) %>%
  add_osm_feature(key = "amenity", value = c("restaurant", "bar"))

El segundo paso es utilizar la función osmdata_sf() para descargar toda la información seleccionada en el paso anterior:

gastronomia_comuna1 <- osmdata_sf(gastronomia_comuna1)

Veamos que nos descargamos:

gastronomia_comuna1

Nos pudimos descargar toda la información relacionada a restaurantes y bares que se encontraron dentro de los límites establecidos: 780 puntos, 0 líneas y 37 polígonos.

Sin embargo solo necesitamos quedarnos con los puntos que representan la ubicación exacta de los sitios. Por lo tanto, el paso 3 es seleccionarlos con gastronomia_comuna1\(osm_points* (cabe destacar que, si estaríamos buscando quedarnos con las líneas utilizaríamos *gastronomia_comuna1\)osm_lines y con los polígonos gastronomia_comuna1$osm_polygons):

gastronomia_comuna1 <- gastronomia_comuna1$osm_points

Ahora si ya tenemos nuestros datos geográficos y podemos explorar el tamaño del dataset con dim():

dim(gastronomia_comuna1)
## [1] 780  77

Efectivamente tenemos los 780 puntos junto a 77 columnas/variables. Sumemos esto a nuestro mapa base:

ggmap(mapa_comuna1)+
  geom_sf(data=poligono_comuna1, fill=NA, color="blue", size=1, inherit.aes = FALSE)+
    geom_sf(data=gastronomia_comuna1, inherit.aes = FALSE)+
  labs(title="Restaurantes y Bares",
       subtitle="Comuna 1, CABA",
       caption="Fuente: Open Street Map")+
  theme_void()

Ajustemos un poco la estética de los puntos y demos color a partir de la variable “amenity” que nos indica si el punto corresponde a un restaurant o a un bar:

ggmap(mapa_comuna1)+
  geom_sf(data=poligono_comuna1, fill=NA, color="blue", size=1, inherit.aes = FALSE)+
    geom_sf(data=gastronomia_comuna1, inherit.aes = FALSE, aes(color=amenity))+
  labs(title="Restaurantes y Bares",
       subtitle="Comuna 1, CABA",
       color="Tipo",
       caption="Fuente: Open Street Map")+
  scale_color_manual(values=c("deeppink", "darkgreen"))+
  theme_void()

Los colores elegidos en el mapa anterior y muchos más pueden verlos en este link. También se podrían utilizar los códigos HEX clásicos que los encontrarán en este link.

Podrán ver que los puntos se descargaron de acuerdo al límite del bounding box y no por los límites geográficos de la Comuna 1. Para solucionar esto alcanza con hacer una intersección entre ambos datos geográficos utilizando st_intersection():

gastronomia_comuna1 <- st_intersection(gastronomia_comuna1, poligono_comuna1)

Veamos el resultado:

dim(gastronomia_comuna1)
## [1] 691  77

Ahora que ajustamos los límites, pudimos detectar que en la Comuna 1 hay 691 restaurantes y bares. Veamos esto en el mapa:

ggmap(mapa_comuna1)+
  geom_sf(data=poligono_comuna1, fill=NA, color="blue", size=1, inherit.aes = FALSE)+
    geom_sf(data=gastronomia_comuna1, inherit.aes = FALSE, aes(color=amenity))+
  labs(title="Restaurantes y Bares",
       subtitle="Comuna 1, CABA",
       color="Tipo",
       caption="Fuente: Open Street Map")+
  scale_color_manual(values=c("deeppink", "darkgreen"))+
  theme_void()

A simple vista, parece que hay mayor cantidad de restaurantes que de bares, y que hay una cantidad pequeña de sitios de tipo “amenity” que no fueron categorizados. Hagamos un cálculo más preciso:

gastronomia_comuna1 %>%
  group_by(amenity) %>%
  summarise(cantidad=n())
## Simple feature collection with 3 features and 2 fields
## Geometry type: MULTIPOINT
## Dimension:     XY
## Bounding box:  xmin: -58.39271 ymin: -34.63391 xmax: -58.3611 ymax: -34.58247
## Geodetic CRS:  WGS 84
## # A tibble: 3 × 3
##   amenity    cantidad                                                   geometry
##   <chr>         <int>                                           <MULTIPOINT [°]>
## 1 bar              88 ((-58.37009 -34.61835), (-58.37086 -34.61868), (-58.37151…
## 2 restaurant      441 ((-58.37059 -34.62525), (-58.37074 -34.62527), (-58.37123…
## 3 <NA>            162 ((-58.37078 -34.62364), (-58.37099 -34.62365), (-58.37103…

Finalmente, podemos ver que en la Comuna 1 hay 441 restaurantes, 162 amenities sin categoría y 88 bares.

Mapas interactivos

Hasta acá hemos estado trabajando solo con mapas “estáticos”. Sin embargo, trabajar con información geográfica también nos permite desarrollar mapas interactivos que posibilitan que los usuarios puedan desplazarse sobre él, hacer zoom, consultar información relacionada a una geometría (línea, punto, polígono), etc.

En la actualidad existen varias herramientas que nos permiten desarrollar este tipo de mapas, pero hoy trabajaremos con Leaflet, una librería open source escrita en JavaScript. Cualquier duda, pueden revisar la documentación oficial en este link.

Procedamos a instalar y activar leaflet:

#install.packages("leaflet")
library(leaflet)

Y comencemos a desarrollar nuestro primer mapa interactivo con leaflet():

leaflet()

Como verán quedó vacío, así que agreguemos la información mínima que necesitamos para poder visibilizar resultados:

  1. Primero se escribe la función leaflet().
  2. Luego es necesario sumar la función addTiles() que nos permite agregar un mapa de fondo. Por defecto se agrega el de OpenStreetMap, así que en caso de que queramos elegir nosotros mismos que mapa de fondo asignar, podríamos utilizar la función addProviderTiles().
  3. Sumar una función que indique en que “formato” y qué información (data) queremos mapear. Por ejemplo, podríamos agregar el polígono con los límites de la Comuna 1 escribiendo addPolygons(data=poligono_comuna1).
leaflet() %>%
  addTiles() %>%
  addPolygons(data=poligono_comuna1)

Si en cambio, queremos mostrar los puntos de los restaurantes y bares podemos utilizar la función addMarkers(data=gastronomia_comuna1).

leaflet() %>%
  addTiles() %>%
  addMarkers(data=gastronomia_comuna1)

Y también podemos cambiar el mapa base, eligiendo por ejemplo “CartoDB.Positron”. Pueden ver otros proveedores de mapas aquí.

leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addMarkers(data=gastronomia_comuna1)

Listo, ya creamos nuestro primer mapa interactivo con información de locales gastronómicos en la Comuna 1. Pero todavía podríamos agregar información extra que nos ayude a sacar mayores conclusiones al recorrer el mapa, por ejemplo un “pop up” con el tipo de amenity (restaurant o bar) y el nombre.

Para esto, primero eliminemos aquellos registros que tienen valores nulos en dichos campos y ajustemos el encoding para evitar que tildes y “ñ” salgan mal:

gastronomia_comuna1 <- gastronomia_comuna1 %>%
          filter(!is.na(name), !is.na(amenity)) %>%
  mutate_if(is.character, iconv, from="UTF-8", to="latin1" )

Ahora si, hagamos el mapa con todos los ajustes necesarios:

leaflet(gastronomia_comuna1) %>%
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addCircleMarkers(popup = paste("Tipo:", gastronomia_comuna1$amenity, "<br>",
                           "Nombre:", gastronomia_comuna1$name),
                   color = ~colorFactor(palette=c("seagreen4","deeppink4"), 
                                        levels=gastronomia_comuna1$amenity)(amenity))%>%
  addLegend("bottomright", pal = colorFactor(palette = c("seagreen4","deeppink4"), 
               levels = gastronomia_comuna1$amenity), values = ~amenity, title = "Tipo", opacity = 1)

Para conocer más ajustes y ejemplos con leaflet, pueden visitar este link.

Ejercicios de práctica

  1. Elegir una Ciudad del mundo, descargar de OpenStreetMap su grilla de calles y mapearla por uno de sus atributos (velocidad mínima, velocidad máxima, cantidad de carriles, etc).

  2. Descargar de OpenStreetMap una (o más) capas de datos de tipo puntos o polígonos.

  3. Proyectar los datos descargados en un mapa y comentar los resultados: ¿Cómo se distribuyen en la Ciudad?